#include <algorithm>
#include <cstdio>
#include <sstream>

#include "collectionstack.h" // IWYU pragma: keep
#include "scanner.h"
#include "singledocparser.h"
#include "tag.h"
#include "token.h"
#include "yaml-cpp/depthguard.h"
#include "yaml-cpp/emitterstyle.h"
#include "yaml-cpp/eventhandler.h"
#include "yaml-cpp/exceptions.h" // IWYU pragma: keep
#include "yaml-cpp/mark.h"
#include "yaml-cpp/null.h"

namespace YAML {
SingleDocParser::SingleDocParser(Scanner& scanner, const Directives& directives)
    : m_scanner(scanner), m_directives(directives),
      m_pCollectionStack(new CollectionStack), m_anchors{}, m_curAnchor(0) {}

SingleDocParser::~SingleDocParser() = default;

// HandleDocument
// . Handles the next document
// . Throws a ParserException on error.
void SingleDocParser::HandleDocument(EventHandler& eventHandler) {
    assert(!m_scanner.empty()); // guaranteed that there are tokens
    assert(!m_curAnchor);

    eventHandler.OnDocumentStart(m_scanner.peek().mark);

    // eat doc start
    if (m_scanner.peek().type == Token::DOC_START)
        m_scanner.pop();

    // recurse!
    HandleNode(eventHandler);

    eventHandler.OnDocumentEnd();

    // and finally eat any doc ends we see
    while (!m_scanner.empty() && m_scanner.peek().type == Token::DOC_END)
        m_scanner.pop();
}

void SingleDocParser::HandleNode(EventHandler& eventHandler) {
    DepthGuard<500> depthguard(depth, m_scanner.mark(), ErrorMsg::BAD_FILE);

    // an empty node *is* a possibility
    if (m_scanner.empty()) {
        eventHandler.OnNull(m_scanner.mark(), NullAnchor);
        return;
    }

    // save location
    Mark mark = m_scanner.peek().mark;

    // special case: a value node by itself must be a map, with no header
    if (m_scanner.peek().type == Token::VALUE) {
        eventHandler.OnMapStart(mark, "?", NullAnchor, EmitterStyle::Default);
        HandleMap(eventHandler);
        eventHandler.OnMapEnd();
        return;
    }

    // special case: an alias node
    if (m_scanner.peek().type == Token::ALIAS) {
        eventHandler.OnAlias(mark, LookupAnchor(mark, m_scanner.peek().value));
        m_scanner.pop();
        return;
    }

    std::string tag;
    std::string anchor_name;
    anchor_t anchor;
    ParseProperties(tag, anchor, anchor_name);

    if (!anchor_name.empty())
        eventHandler.OnAnchor(mark, anchor_name);

    // after parsing properties, an empty node is again a possibility
    if (m_scanner.empty()) {
        eventHandler.OnNull(mark, anchor);
        return;
    }

    const Token& token = m_scanner.peek();

    // add non-specific tags
    if (tag.empty())
        tag = (token.type == Token::NON_PLAIN_SCALAR ? "!" : "?");

    if (token.type == Token::PLAIN_SCALAR && tag.compare("?") == 0 &&
        IsNullString(token.value)) {
        eventHandler.OnNull(mark, anchor);
        m_scanner.pop();
        return;
    }

    // now split based on what kind of node we should be
    switch (token.type) {
    case Token::PLAIN_SCALAR:
    case Token::NON_PLAIN_SCALAR:
        eventHandler.OnScalar(mark, tag, anchor, token.value);
        m_scanner.pop();
        return;
    case Token::FLOW_SEQ_START:
        eventHandler.OnSequenceStart(mark, tag, anchor, EmitterStyle::Flow);
        HandleSequence(eventHandler);
        eventHandler.OnSequenceEnd();
        return;
    case Token::BLOCK_SEQ_START:
        eventHandler.OnSequenceStart(mark, tag, anchor, EmitterStyle::Block);
        HandleSequence(eventHandler);
        eventHandler.OnSequenceEnd();
        return;
    case Token::FLOW_MAP_START:
        eventHandler.OnMapStart(mark, tag, anchor, EmitterStyle::Flow);
        HandleMap(eventHandler);
        eventHandler.OnMapEnd();
        return;
    case Token::BLOCK_MAP_START:
        eventHandler.OnMapStart(mark, tag, anchor, EmitterStyle::Block);
        HandleMap(eventHandler);
        eventHandler.OnMapEnd();
        return;
    case Token::KEY:
        // compact maps can only go in a flow sequence
        if (m_pCollectionStack->GetCurCollectionType() ==
            CollectionType::FlowSeq) {
            eventHandler.OnMapStart(mark, tag, anchor, EmitterStyle::Flow);
            HandleMap(eventHandler);
            eventHandler.OnMapEnd();
            return;
        }
        break;
    default:
        break;
    }

    if (tag == "?")
        eventHandler.OnNull(mark, anchor);
    else
        eventHandler.OnScalar(mark, tag, anchor, "");
}

void SingleDocParser::HandleSequence(EventHandler& eventHandler) {
    // split based on start token
    switch (m_scanner.peek().type) {
    case Token::BLOCK_SEQ_START:
        HandleBlockSequence(eventHandler);
        break;
    case Token::FLOW_SEQ_START:
        HandleFlowSequence(eventHandler);
        break;
    default:
        break;
    }
}

void SingleDocParser::HandleBlockSequence(EventHandler& eventHandler) {
    // eat start token
    m_scanner.pop();
    m_pCollectionStack->PushCollectionType(CollectionType::BlockSeq);

    while (true) {
        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_SEQ);

        Token token = m_scanner.peek();
        if (token.type != Token::BLOCK_ENTRY &&
            token.type != Token::BLOCK_SEQ_END)
            throw ParserException(token.mark, ErrorMsg::END_OF_SEQ);

        m_scanner.pop();
        if (token.type == Token::BLOCK_SEQ_END)
            break;

        // check for null
        if (!m_scanner.empty()) {
            const Token& nextToken = m_scanner.peek();
            if (nextToken.type == Token::BLOCK_ENTRY ||
                nextToken.type == Token::BLOCK_SEQ_END) {
                eventHandler.OnNull(nextToken.mark, NullAnchor);
                continue;
            }
        }

        HandleNode(eventHandler);
    }

    m_pCollectionStack->PopCollectionType(CollectionType::BlockSeq);
}

void SingleDocParser::HandleFlowSequence(EventHandler& eventHandler) {
    // eat start token
    m_scanner.pop();
    m_pCollectionStack->PushCollectionType(CollectionType::FlowSeq);

    while (true) {
        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_SEQ_FLOW);

        // first check for end
        if (m_scanner.peek().type == Token::FLOW_SEQ_END) {
            m_scanner.pop();
            break;
        }

        // then read the node
        HandleNode(eventHandler);

        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_SEQ_FLOW);

        // now eat the separator (or could be a sequence end, which we ignore -
        // but if it's neither, then it's a bad node)
        Token& token = m_scanner.peek();
        if (token.type == Token::FLOW_ENTRY)
            m_scanner.pop();
        else if (token.type != Token::FLOW_SEQ_END)
            throw ParserException(token.mark, ErrorMsg::END_OF_SEQ_FLOW);
    }

    m_pCollectionStack->PopCollectionType(CollectionType::FlowSeq);
}

void SingleDocParser::HandleMap(EventHandler& eventHandler) {
    // split based on start token
    switch (m_scanner.peek().type) {
    case Token::BLOCK_MAP_START:
        HandleBlockMap(eventHandler);
        break;
    case Token::FLOW_MAP_START:
        HandleFlowMap(eventHandler);
        break;
    case Token::KEY:
        HandleCompactMap(eventHandler);
        break;
    case Token::VALUE:
        HandleCompactMapWithNoKey(eventHandler);
        break;
    default:
        break;
    }
}

void SingleDocParser::HandleBlockMap(EventHandler& eventHandler) {
    // eat start token
    m_scanner.pop();
    m_pCollectionStack->PushCollectionType(CollectionType::BlockMap);

    while (true) {
        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_MAP);

        Token token = m_scanner.peek();
        if (token.type != Token::KEY && token.type != Token::VALUE &&
            token.type != Token::BLOCK_MAP_END)
            throw ParserException(token.mark, ErrorMsg::END_OF_MAP);

        if (token.type == Token::BLOCK_MAP_END) {
            m_scanner.pop();
            break;
        }

        // grab key (if non-null)
        if (token.type == Token::KEY) {
            m_scanner.pop();
            HandleNode(eventHandler);
        }
        else {
            eventHandler.OnNull(token.mark, NullAnchor);
        }

        // now grab value (optional)
        if (!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
            m_scanner.pop();
            HandleNode(eventHandler);
        }
        else {
            eventHandler.OnNull(token.mark, NullAnchor);
        }
    }

    m_pCollectionStack->PopCollectionType(CollectionType::BlockMap);
}

void SingleDocParser::HandleFlowMap(EventHandler& eventHandler) {
    // eat start token
    m_scanner.pop();
    m_pCollectionStack->PushCollectionType(CollectionType::FlowMap);

    while (true) {
        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_MAP_FLOW);

        Token& token = m_scanner.peek();
        const Mark mark = token.mark;
        // first check for end
        if (token.type == Token::FLOW_MAP_END) {
            m_scanner.pop();
            break;
        }

        // grab key (if non-null)
        if (token.type == Token::KEY) {
            m_scanner.pop();
            HandleNode(eventHandler);
        }
        else {
            eventHandler.OnNull(mark, NullAnchor);
        }

        // now grab value (optional)
        if (!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
            m_scanner.pop();
            HandleNode(eventHandler);
        }
        else {
            eventHandler.OnNull(mark, NullAnchor);
        }

        if (m_scanner.empty())
            throw ParserException(m_scanner.mark(), ErrorMsg::END_OF_MAP_FLOW);

        // now eat the separator (or could be a map end, which we ignore - but
        // if it's neither, then it's a bad node)
        Token& nextToken = m_scanner.peek();
        if (nextToken.type == Token::FLOW_ENTRY)
            m_scanner.pop();
        else if (nextToken.type != Token::FLOW_MAP_END)
            throw ParserException(nextToken.mark, ErrorMsg::END_OF_MAP_FLOW);
    }

    m_pCollectionStack->PopCollectionType(CollectionType::FlowMap);
}

// . Single "key: value" pair in a flow sequence
void SingleDocParser::HandleCompactMap(EventHandler& eventHandler) {
    m_pCollectionStack->PushCollectionType(CollectionType::CompactMap);

    // grab key
    Mark mark = m_scanner.peek().mark;
    m_scanner.pop();
    HandleNode(eventHandler);

    // now grab value (optional)
    if (!m_scanner.empty() && m_scanner.peek().type == Token::VALUE) {
        m_scanner.pop();
        HandleNode(eventHandler);
    }
    else {
        eventHandler.OnNull(mark, NullAnchor);
    }

    m_pCollectionStack->PopCollectionType(CollectionType::CompactMap);
}

// . Single ": value" pair in a flow sequence
void SingleDocParser::HandleCompactMapWithNoKey(EventHandler& eventHandler) {
    m_pCollectionStack->PushCollectionType(CollectionType::CompactMap);

    // null key
    eventHandler.OnNull(m_scanner.peek().mark, NullAnchor);

    // grab value
    m_scanner.pop();
    HandleNode(eventHandler);

    m_pCollectionStack->PopCollectionType(CollectionType::CompactMap);
}

// ParseProperties
// . Grabs any tag or anchor tokens and deals with them.
void SingleDocParser::ParseProperties(std::string& tag, anchor_t& anchor,
                                      std::string& anchor_name) {
    tag.clear();
    anchor_name.clear();
    anchor = NullAnchor;

    while (true) {
        if (m_scanner.empty())
            return;

        switch (m_scanner.peek().type) {
        case Token::TAG:
            ParseTag(tag);
            break;
        case Token::ANCHOR:
            ParseAnchor(anchor, anchor_name);
            break;
        default:
            return;
        }
    }
}

void SingleDocParser::ParseTag(std::string& tag) {
    Token& token = m_scanner.peek();
    if (!tag.empty())
        throw ParserException(token.mark, ErrorMsg::MULTIPLE_TAGS);

    Tag tagInfo(token);
    tag = tagInfo.Translate(m_directives);
    m_scanner.pop();
}

void SingleDocParser::ParseAnchor(anchor_t& anchor, std::string& anchor_name) {
    Token& token = m_scanner.peek();
    if (anchor)
        throw ParserException(token.mark, ErrorMsg::MULTIPLE_ANCHORS);

    anchor_name = token.value;
    anchor = RegisterAnchor(token.value);
    m_scanner.pop();
}

anchor_t SingleDocParser::RegisterAnchor(const std::string& name) {
    if (name.empty())
        return NullAnchor;

    return m_anchors[name] = ++m_curAnchor;
}

anchor_t SingleDocParser::LookupAnchor(const Mark& mark,
                                       const std::string& name) const {
    auto it = m_anchors.find(name);
    if (it == m_anchors.end())
        throw ParserException(mark, ErrorMsg::UNKNOWN_ANCHOR);

    return it->second;
}
} // namespace YAML
